package com.evolved.automata.experimental;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.util.concurrent.*;
import java.util.*;


import com.evolved.automata.experimental.bots.SRV1Robot;

public class AgentManager implements UtilityListener, Serializer {

	
	
	HashMap<String, AgentInfo> j_AgentMap;
	LinkedBlockingQueue<AgentCommand> j_CommandQueue;
	LinkedList<Agent> j_Agents;
	Thread j_CommandThread;
	HashMap<String, String> j_AgentGeneMap;
	HashMap<String, GeneManager> j_GeneMap;


	public AgentManager()
	{
		j_SerializerHelper = new SerializerAdapter(this);
		j_CommandQueue = new LinkedBlockingQueue<AgentCommand>();
		j_AgentGeneMap = new HashMap<String, String>();
		j_AgentMap = new HashMap<String, AgentInfo>();
		j_GeneMap = new HashMap<String, GeneManager>();
	}
	
	public AgentManager(String baseName)
	{
		j_SerializerHelper = new SerializerAdapter(this);
		j_CommandQueue = new LinkedBlockingQueue<AgentCommand>();
		j_AgentGeneMap = new HashMap<String, String>();
		j_AgentMap = new HashMap<String, AgentInfo>();
		j_UniqueName = GetTypeName()+"."+baseName;
		j_SerializerHelper.SetUniqueName(j_UniqueName);
		j_GeneMap = new HashMap<String, GeneManager>();
	}
	
	
	public AgentManager(String initialPoolName,int poolSize, int nextGenerationSize)
	{
		j_AgentGeneMap = new HashMap<String, String>();
		j_SerializerHelper = new SerializerAdapter(this);
		j_CommandQueue = new LinkedBlockingQueue<AgentCommand>();
		j_GeneMap = new HashMap<String, GeneManager>();
		CreateGeneManager(initialPoolName,poolSize, nextGenerationSize);
		j_AgentMap = new HashMap<String, AgentInfo>();
	}
	
	
	//TODO: Use a custom exception instead of boolean return value
	public boolean CreateGeneManager(String name, int poolSize, int nextGenerationSize)
	{
		if (j_GeneMap.containsKey(name))
			return false;
		j_GeneMap.put(name, new GeneManager(poolSize,nextGenerationSize));
		return true;
	}
	
	//TODO: Use a custom exception instead of boolean return value
	public boolean CreateGeneManager(String name, int poolSize, int nextGenerationSize, int selectionWidth)
	{
		if (j_GeneMap.containsKey(name))
			return false;
		j_GeneMap.put(name, new GeneManager(poolSize,nextGenerationSize,selectionWidth));
		return true;
	}
	
	
	public void ContinueEvolution()
	{
		if (j_CommandThread==null || !j_CommandThread.isAlive())
			ExecuteCommandLoop();
		AddCommandToQueue(AgentCommand.Command.CONTINUE_EVOLUTION,null);
		
			
	}
	
	
	
	public void PauseEvolution()
	{
		if (j_CommandThread!=null&&j_CommandThread.isAlive())
			AddCommandToQueue(AgentCommand.Command.EXIT,null);
	}
	
	private void AddCommandToQueue(AgentCommand.Command commandType, Object data)
	{
		try
		{
			j_CommandQueue.put(new AgentCommand(commandType,data));
		}
		catch (Exception e)
		{
			
		}
	}
	public void UpdateUtility(String agentShort, UtilityAndPerferenceModule.Utility utility)
	{
		AgentInfo info = j_AgentMap.get(agentShort);
		info.Update(utility);
	}
	
	// TODO: Use a custom exception instead of boolean return value
	public boolean AddAutomaton(String agentName, StaticTools.AgentType aType,StaticTools.SensoryMototProviderType smpType, StaticTools.GenomeType gType, String genePoolName )
	{
		UtilityAndPerferenceModule module;
		SensoryMotorDataProvider provider;
		String uniqueName;
		GeneManager manager = j_GeneMap.get(genePoolName);
		
		if (manager==null)
			return false; 
		if (ComboMatch(aType,smpType,gType,StaticTools.AgentType.AUTONOMOUS_ROVER_TRAINER, StaticTools.SensoryMototProviderType.SRV1, StaticTools.GenomeType.SIMPLE_PROBABILITSTIC))
		{
			j_AgentGeneMap.put(agentName, genePoolName);
			uniqueName= j_UniqueName+".ProbabilisticLearningAgent."+agentName;
			module = new SimpleRobotUtilityInterpreter(this,uniqueName,4,10);
			provider = new SRV1SensoryMotorProvider();
			ProbabilisticGenome genom = new ProbabilisticGenome();
			// make initial fintess into a variable
			genom.SetFitness(manager.GetBaseFitness());
			ProbabilisticLearningAgent agent = new ProbabilisticLearningAgent(uniqueName,genom,module,4,4,4,SRV1Robot.STOP );
			agent.UpdateBaseInferentialFreedoms(provider.GetBaseSensoryInferentialFreedom());
			
			j_AgentMap.put(agentName, new AgentInfo(provider,agent));
			return true;
		}
		return false;
	}
	
	private boolean ComboMatch(StaticTools.AgentType aLType,StaticTools.SensoryMototProviderType smpLType, StaticTools.GenomeType gLType,StaticTools.AgentType aRType,StaticTools.SensoryMototProviderType smpRType, StaticTools.GenomeType gRType) 
	{
		return (aLType==aRType)&&(smpLType==smpRType)&&(gLType==gRType);
	}
	
	/*
	 * Creates a thread with the new command loop and starts the thread.  This methods should only be
	 * called once.
	 */
	private void ExecuteCommandLoop()
	{
		Runnable loop = new Runnable()
		{
			public void run()
			{
				AgentCommand com;
				AgentInfo info;
				Genome[] offspring;
				Genome newGenome;
				GeneManager poolManager;
				int fitness;
				boolean first=true;
				try
				{
					com = j_CommandQueue.take();
					while (com.GetCommandType()!=AgentCommand.Command.EXIT)
					{
						switch (com.GetCommandType())
						{
							case CONTINUE_EVOLUTION:
								String[] agentList = j_AgentMap.keySet().toArray(new String[0]);
								if ((agentList!=null)&&(agentList.length>0))
								{
									for (String name: agentList)
									{
										poolManager = j_GeneMap.get(j_AgentGeneMap.get(name));
										info = j_AgentMap.get(name);
										if (info.AliveP())
										{
											info.UpdateAgent();
										}
										else
										{
											if (!info.PausedP())
											{
												if (!info.KilledP())
												{
													first=true;
													offspring=info.GetAgent().GetTotalGenome();
													for (Genome g: offspring)
													{
														fitness = info.GetFitness();
														if (first)
														{
															// The parent genome gets the fitness that it originally
															// started with whereas the offspring genome gets the fitness
															// that was newly accumulated
															g.SetFitness(g.GetFitness()+fitness);
															first=false;
														}
														else
															g.SetFitness(poolManager.GetBaseFitness()+fitness);
														poolManager.AddGenome(g);
													}
												}
												newGenome=(ProbabilisticGenome)poolManager.GetGenome();
												info.GetAgent().GeneSwap(newGenome);
												info.ResetFitness(0);
											}
											
										}
									}
								}
								j_CommandQueue.put(new AgentCommand(AgentCommand.Command.CONTINUE_EVOLUTION,null));
								break;
							case EXIT:
								return;
						}
						com = j_CommandQueue.take();
					}
				}
				catch (InterruptedException e)
				{
					
				}
			}
		};
		j_CommandThread = new Thread(loop);
		j_CommandThread.start();
	}
	
	
	
	/*
	 * Serialization Related Fields and Methods
	 * 
	 * 
	 */
	public enum LoadState
	{
		LOADING_GENE_COUNT, LOADING_AGENT_GENE_MAP, LOADING_GENEMANAGER, LOADING_AGENT_INFOS,EXIT
	}
	
	
	public enum SaveState
	{
		SAVING_GENE_COUNT, SAVING_AGENT_GENE_MAP,SAVING_GENEMANAGER, SAVING_AGENT_INFOS,EXIT
	}
	
	
	private LoadState j_CurrentState;
	
	/* 
	 * @see com.evolved.automata.experimental.Serializer#AddData(java.lang.String)
	 */
	private SerializerAdapter j_SerializerHelper;
	private String j_UniqueName;
	private int j_GeneCounter;

	public void SaveData(BufferedWriter writer) {
		SaveState currentState=SaveState.SAVING_GENEMANAGER;
		// Must traverse save state machine in same basic order as load machine is expected to be traversed
		AgentInfo info;
		while (currentState!=SaveState.EXIT)
		{
			switch (currentState)
			{
				case SAVING_GENE_COUNT:
					if ((j_GeneMap==null)||(j_GeneMap.size()==0))
					{	
						StaticTools.WriteRawData(writer, 0);
						return;
					}
					else
					{
						StaticTools.WriteRawData(writer, j_GeneMap.size());
						currentState=SaveState.SAVING_AGENT_GENE_MAP;
					}
					break;
				case SAVING_AGENT_GENE_MAP:
					boolean first=true;
					String geneName;
					StringBuilder sBuilder = new StringBuilder("");
					for (String agentName:j_AgentGeneMap.keySet())
					{
						geneName = j_AgentGeneMap.get(agentName);
						
						if (first)
						{
							sBuilder.append(String.format("%1s$|%2s$", agentName,geneName));
							first=false;
						}
						else
							sBuilder.append(String.format(",%1s$|%2s$", agentName,geneName));
					}
					StaticTools.WriteRawData(writer, sBuilder.toString());
					currentState=SaveState.SAVING_GENEMANAGER;
					break;
				case  SAVING_GENEMANAGER:
					GeneManager manager;
					for (String managerName: j_GeneMap.keySet())
					{
						manager = j_GeneMap.get(managerName);
						manager.SetName(j_SerializerHelper.ConstructUniqueName(manager.GetTypeName(), managerName));
						manager.Save(writer);
					}
					
					currentState=SaveState.SAVING_AGENT_INFOS;
					break;
				case SAVING_AGENT_INFOS:
					for (String infoName:j_AgentMap.keySet())
					{
						info = j_AgentMap.get(infoName);
						info.SetName(j_SerializerHelper.ConstructUniqueName(info.GetTypeName(), infoName));
						info.Save(writer);
					}
					currentState=SaveState.EXIT;
					break;
					
			}
		}

	}
	
	
	/*
	 * Use this to load simple data values
	 */
	public void AddData(String dataLine) {
		// Must traverse load state machine in same basic order as data is saved in SaveData
		switch (j_CurrentState)
		{
			// Add load code
			case LOADING_GENE_COUNT:
				j_GeneCounter=Integer.parseInt(dataLine);
				if (j_GeneCounter==0)
					j_CurrentState=LoadState.EXIT;
				else
					j_CurrentState=LoadState.LOADING_AGENT_GENE_MAP;
				break;
			case LOADING_AGENT_GENE_MAP:
				String[] keyMap, keyPair;
				String  agentName, managerName;
				if (dataLine.length()>0)
				{
					keyMap = StaticTools.SplitData(dataLine, ",");
					for (String pair: keyMap)
					{
						keyPair = StaticTools.SplitData(pair,"|");
						agentName = keyPair[0];
						managerName = keyPair[1];
						j_AgentGeneMap.put(agentName, managerName);
					}
				}
				j_CurrentState=LoadState.LOADING_GENEMANAGER;
				break;
		}
	}

	

	
	
	/* 
	 * Use this to add Serializers
	 */
	public void AddData(Serializer object, String instanceId) {
		// Must traverse load state machine in same basic order as data is saved in SaveData
		switch (j_CurrentState)
		{
			case LOADING_GENEMANAGER:
				if (j_GeneCounter==0)
				{
					j_CurrentState=LoadState.LOADING_AGENT_INFOS;
					return;
				}
				j_GeneCounter--;
				GeneManager manager = (GeneManager)object;
				j_GeneMap.put(instanceId, manager);
				
				break;
			case LOADING_AGENT_INFOS:
				AgentInfo info;
				info = (AgentInfo)object;
				j_AgentMap.put(instanceId, info);
				break;
			case EXIT:
				break;
		}
	}

	
	public String GetTypeName() {
		
		return "AgentManager";
	}

	/* 
	 * @see com.evolved.automata.experimental.Serializer#PrepareSerialization()
	 */
	
	public void PrepareSerialization() {
		  
		j_CurrentState=LoadState.LOADING_GENE_COUNT;
	}
	
	
	
	/* ********************
	 * Standard methods Below.  Use the following standard code snippets for this.
	 * ********************
	 */
	
	public void Load(BufferedReader reader)
	{
		j_SerializerHelper.Load(reader);
	}
	
	
	public void Load(String fileFullName)
	{
		j_SerializerHelper.Load(fileFullName);
		
	}



	public void Save(String fileFullName)
	{
		j_SerializerHelper.Save(fileFullName);
	}


	public  void Save(BufferedWriter writer)
	{
		j_SerializerHelper.Save(writer);
	}

	
	public void SetName(String uniqueName)
	{
		j_UniqueName=uniqueName;
		j_SerializerHelper.SetUniqueName(uniqueName);
	}
	
	
}
